/************************************************************************************
* PlaylistModel.cpp                                                                 *
*                                                                                   *
* Copyright (C) 2011 Varuna L Amachi                                                *
*                                                                                   *
* This program is free software; you can redistribute it and/or modify              *
* it under the terms of the GNU General Public License as published by              *
* the Free Software Foundation; either version 2 of the License, or any             *
* later version.                                                                    *
*                                                                                   *
* This program is distributed in the hope that it will be useful, but               *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY        *
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for       *
* more details. You should have received a copy of the GNU General Public           *
* License along with this program; if not, write to the Free Software Foundation,   *
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA                       *
*                                                                                   *
*************************************************************************************/


#include "PlaylistModel.h"
#include "common/Utils.h"
#include "common/Constants.h"

#include <QtAlgorithms>
#include <QBrush>
#include <QSet>
#include <QFont>



PlaylistModel::PlaylistModel(QObject* parent) :
    QAbstractTableModel(parent),
    m_currentIndex(-1),  //Minus one means.. well there's nothin here
    m_prevListIndex(-1),
    m_randomize(false),
    m_loopType(RepeatAll) {
    connect(this, SIGNAL(selected(MediaItem*,int)), this, SLOT(notifySelected(MediaItem*)));
}


bool PlaylistModel::hasNext() {
    if(m_sources.empty() || m_currentIndex == -1) {
        return false;
    }
    else if(! m_randomize && m_loopType == NoRepeat) {
        if(m_currentIndex == m_sources.size() -1) {
            return false;
        }
    }
    return true;
}




bool PlaylistModel::hasPrev() {
    return  !( m_sources.empty()
               || m_currentIndex == -1
               || m_prevListIndex <= 0
               || m_playedIndexList.empty());
}


bool PlaylistModel::isRandom() {
    return m_randomize;
}



bool PlaylistModel::isValid() {
    return (m_sources.empty() || m_currentIndex < 0);
}



qint32 PlaylistModel::currentIndex() {
    return m_currentIndex;
}


MediaItem* PlaylistModel::currentTrack() {
    if (isValid()) {
        return m_sources.at(m_currentIndex);
    }
    return NULL;
}


quint32 PlaylistModel::numItems() {
    return m_sources.size();
}



void PlaylistModel::selectNext() {
    if (hasNext()) {
        setNextIndex();
        MediaItem* item = m_sources.at(m_currentIndex);
        emit selected(item, m_currentIndex);
    }
}


void PlaylistModel::selectPrev() {
    if (hasPrev()) {
        if(m_prevListIndex == m_playedIndexList.size()) {
            m_playedIndexList.append(m_currentIndex);
        }
        //This while is to avoid one peculiar condition of duplicate
        //entries in the played index list. Once the reason for that is
        //found out, looping will no longer be nesessary.
        while(m_prevListIndex > 0 && m_currentIndex == m_playedIndexList[m_prevListIndex]) {
            -- m_prevListIndex;
        }
        m_currentIndex = m_playedIndexList.at( m_prevListIndex);
        emit selected(m_sources[ m_currentIndex ], m_currentIndex);
    }
}



void PlaylistModel::selectIndex(int index) {
    if (! m_sources.isEmpty() &&
         index < m_sources.size() &&
         index > -1) {
        appendPlayedIndex(m_currentIndex);
        m_currentIndex    =  index;
        MediaItem* item = m_sources.at(m_currentIndex);
        emit selected(item, m_currentIndex);
    }
}



void PlaylistModel::selectIndex(QModelIndex index) {
    if(index.isValid() && index.row() <= m_sources.size()) {
        appendPlayedIndex(m_currentIndex);
        m_currentIndex = index.row();
        emit selected(m_sources.at(m_currentIndex), m_currentIndex);
    }
}




void PlaylistModel::setRandom(bool random) {
    if(m_randomize != random) {
        m_randomize = random;
        cleanTempStructs(true);
        emit randomConfigChanged(random);
    }
}

void PlaylistModel::setRepeat (LoopType loopType) {
    if(m_loopType != loopType) {
        LoopType oldType = m_loopType;
        m_loopType = loopType;
        cleanTempStructs(true);
        repeatConfigChanged(m_loopType, oldType);
    }
}



void PlaylistModel::append(MediaItem* item, bool play) {
    if (item != NULL) {
        int index = m_sources.size()?m_sources.size() - 1 : 0;
        beginInsertRows(QModelIndex(), index, index);
        m_sources.append(item);
        if ( play ) {
            appendPlayedIndex(m_currentIndex);
            m_currentIndex = m_sources.size() - 1;
            emit selected(item, m_currentIndex);
        }
        endInsertRows();
        emit playlistChanged();
    }
}


void PlaylistModel::append(QList<MediaItem*>* items, bool play) {
    if (items != NULL && ! items->empty()) {
        beginInsertRows(QModelIndex(), m_sources.size(), m_sources.size()+items->size()-1);
        int prevLastIndex = m_sources.size() - 1;
        m_sources.append((*items));
        if (play) {
            appendPlayedIndex(m_currentIndex);
            m_currentIndex = prevLastIndex + 1;
            emit selected(m_sources.at(m_currentIndex), m_currentIndex);
        }
        endInsertRows();
        emit playlistChanged();
    }
}



void PlaylistModel::insertAsCurrent(MediaItem *item, bool play) {
    if (item != NULL) {
        beginInsertRows(QModelIndex(), m_currentIndex, m_currentIndex);
        m_sources.insert(m_currentIndex, item);
        if (play) {
            emit selected(item, m_currentIndex);
        }
        endInsertRows();
        cleanTempStructs();
        emit playlistChanged();
    }
}


void PlaylistModel::insert(int index, MediaItem* item, bool play) {
    if(item != NULL && index < m_sources.size()) {
        beginInsertRows(QModelIndex(), index, index);
        m_sources.insert(index, item);
        if(play) {
            m_currentIndex = index;
            selected(m_sources.at(m_currentIndex), m_currentIndex);
        }
        endInsertRows();
        cleanTempStructs();
        emit playlistChanged();
    }
}


void PlaylistModel::removeItem(int index) {
    if ( index >= 0 && index < m_sources.size()) {
        MediaItem* delItem = NULL;
        beginRemoveRows(QModelIndex(), index, index);
        if(m_sources.size() == 1) {
            m_sources.clear();
            m_currentIndex = -1;
        }
        else if(index > m_currentIndex) {
            delItem = m_sources.at(index);
            m_sources.removeAt(index);
        }
        else if(index == m_currentIndex) {
            emit removingCurrentItem();
            delItem = m_sources.at(m_currentIndex);
            m_sources.removeAt(m_currentIndex);
            if(m_sources.size() < m_currentIndex) {
                if(m_loopType == RepeatAll) {
                    m_currentIndex = 0;
                    emit selected(m_sources[m_currentIndex], m_currentIndex);
                }
                else {
                    m_currentIndex = -1;
                    emit clearingPlaylist();
                }
            }
        }
        else if(index < m_currentIndex) {
            -- m_currentIndex;
            delItem = m_sources.at(index);
            m_sources.removeAt(index);
        }
        if(delItem != NULL &&
                delItem->itemType() == MediaItem::FILE_SYSTEM_ITEM) {
            delete delItem;
        }
        endRemoveRows();

        if(m_sources.size() == 0) {
            cleanTempStructs(true);
        }
        else {
            for(int j = 0; j < m_playedIndexList.size(); ++j) {
                if(index == m_playedIndexList[j]) {
                    cleanTempStructs();
                    break;
                }
            }
        }
        emit playlistChanged();
    }
}




void PlaylistModel::removeItems(QList<int>& indices) {
    QList<MediaItem*> deletionCandidates;
    qSort(indices.begin(), indices.end());
    bool selectionChange = false;
    for(int i = indices.size() -1; i >= 0; i--) {
        int index = indices.at(i);
        beginRemoveRows(QModelIndex(), index, index);
        if ( index >= 0 && index < m_sources.size()) {
            if(m_sources.size() == 1) {
                emit clearingPlaylist();
                deletionCandidates << m_sources[0];
                m_sources.clear();
                m_currentIndex = -1;
            }
            else if(index > m_currentIndex) {
                deletionCandidates << m_sources[index];
                m_sources.removeAt(index);
            }
            else if(index == m_currentIndex) {
                emit removingCurrentItem();
                deletionCandidates << m_sources[index];
                m_sources.removeAt(m_currentIndex);
                if(m_sources.size() < m_currentIndex) {
                    if(m_loopType == RepeatAll) {
                        m_currentIndex = 0;
                        selectionChange = true;
                    }
                    else {
                        m_currentIndex = -1;
                    }
                }
            }
            else if(index < m_currentIndex) {
//                m_prevIndex = (m_currentIndex == 0 ? -1:m_currentIndex);
                -- m_currentIndex;
                deletionCandidates << m_sources[index];
                m_sources.removeAt(index);

            }
        }
        if(m_sources.empty()) {
            emit clearingPlaylist();
        }
        endRemoveRows();
    }
    foreach(MediaItem* delItem, deletionCandidates) {
        if(delItem->itemType() == MediaItem::FILE_SYSTEM_ITEM) {
            delete delItem;
        }
    }
    if( selectionChange ) {
        emit selected(m_sources[m_currentIndex], m_currentIndex);
    }


    if(m_sources.size() == 0) {
        cleanTempStructs(true);
    }
    else {
        for(int i = 0; i < indices.size(); ++i) {
            for(int j = 0; j < m_playedIndexList.size(); ++j) {
                if(indices[i] == m_playedIndexList[j]) {
                    cleanTempStructs();
                    break;
                }
            }
        }
    }
    emit playlistChanged();
}


void PlaylistModel::clear() {
    clear(true);
}

void PlaylistModel::clear(bool notify) {
    if(! m_sources.empty()) {
        beginRemoveRows(QModelIndex(), 0, m_sources.size()-1);
        emit clearingPlaylist();
        foreach (MediaItem* item, m_sources) {
            if(item->itemType() == MediaItem::FILE_SYSTEM_ITEM) {
                delete item;
            }
        }
        m_sources.clear();
        cleanTempStructs(true);
        m_currentIndex = -1;

        endRemoveRows();
        cleanTempStructs(true);
        foreach(IPlaylistListener* lis, m_listeners) {
            lis->playlistCleared();
        }
    }
    if(notify) {
        emit playlistChanged();
    }
}



PlaylistModel::~PlaylistModel() {
    clear(false);
}



void PlaylistModel::setNextIndex() {
    if (m_loopType == RepeatCurrent) {
        return; //Whatever you do,, your going to listen to same this..
    }
    else if (m_sources.size() == 0) {
        m_currentIndex  = -1;
        m_prevListIndex = -1;
    }
    /*
      This condition comes to play when we are down the previously played
      list and next button is pressed. In this case we'll get the next index
      from the played track index list.
      */
    else if(m_prevListIndex != -1 && m_prevListIndex != m_playedIndexList.size()) {
        /* Below statement will avoid the playing current track once more
          when next button is clicked
         */
        if(m_prevListIndex == 0) {m_prevListIndex = 1;}
        int index = m_currentIndex;
        while(m_prevListIndex < m_playedIndexList.size() &&
              index == m_currentIndex) {
            index= m_playedIndexList[  m_prevListIndex++ ];
        }
        m_currentIndex = index;
    }
    else if(m_prevListIndex == -1 ||
            m_prevListIndex == m_playedIndexList.size()) {
        if (m_randomize) {
            if(m_randTracks.size() == m_sources.size() - 1) {
                m_randTracks.clear();
            }
            int index = m_currentIndex;
            /* Max tries will decide the number of random calculation
              before we give up the calculation of random index since
              there are'nt much available in the range which are not
              played before
             */
            int maxTries = 100;
            while(true) {
                index = qrand() % m_sources.size();
                -- maxTries;
                if(m_sources.size() < 2) {
                    break;
                }
                else if(maxTries) {
                    if (! m_randTracks.contains(m_sources[index]->trackId())) {
                        break;
                    }
                }
                else {
                    if(m_currentIndex != index) {
                        break;
                    }
                }
            }


            if(maxTries == 0) {
                /*If have got the index because max tries are exhausted
                  then it make sense to clear the cache
                 */
                m_randTracks.clear();
            }
            appendPlayedIndex(m_currentIndex);
            m_currentIndex = index;
            m_randTracks.insert(m_sources[m_currentIndex]->trackId());
        }
        else if (m_currentIndex != m_sources.size() -1) {
            appendPlayedIndex(m_currentIndex);
            ++ m_currentIndex;
        }
        else if(! m_randomize && m_loopType == RepeatAll &&
                m_currentIndex == m_sources.size() - 1) {
            appendPlayedIndex(m_currentIndex);
            m_currentIndex = 0;
        }
        else if (m_currentIndex == m_sources.size() - 1) {
//            appendPlayedIndex(m_currentIndex);
            m_currentIndex = -1;
        }
    }
}



LoopType PlaylistModel::repeat() {
    return m_loopType;
}


int PlaylistModel::rowCount(const QModelIndex&) const {
    return m_sources.size();
}


int PlaylistModel::columnCount(const QModelIndex&) const {
    return  2;
}


QVariant PlaylistModel::data(const QModelIndex &index, int role) const {
    if (! index.isValid()) {
        return QVariant();
    }
    if (role == Qt::TextAlignmentRole) {
        return int (Qt::AlignLeft | Qt::AlignVCenter);
    }
    else if (role == Qt::DisplayRole) {
        MediaItem* source = m_sources[index.row()];
        switch(index.column()) {
        case 0: return source->title()+" - "+source->artist();
//        case 1: return source->artist();
//        case 2: return source->album();
        case 1: return Utils::getStringTime(
                        source->duration());
        }
    }
    else if(role == Qt::ToolTipRole) {
        MediaItem* source = m_sources.at(index.row());
        QString tooltip = QString() +
                "Title  : <b>"+source->title()+"</b><br>"+
                "Artist : <b>"+source->artist()+"</b><br>"+
                "Album  : <b>"+source->album()+"</b>";
        return tooltip;
    }
    else if(role == Qt::SizeHintRole) {
        return QSize(0, 16);
    }

    else if(role == Qt::FontRole) {
        return QFont("Droid Sans", 8);
    }
    return QVariant();
}


QVariant PlaylistModel::headerData(int section,
                                   Qt::Orientation orientation,
                                   int role) const {

   if(orientation == Qt::Horizontal) {
        switch(section) {
        case 0: return tr("Track");;
        case 1: return tr("Artist");
        case 2: return tr("Album");
        case 3: return tr("Len");
        }
    }
    else {
        return "";
    }
    return QVariant();
}


QModelIndex PlaylistModel::parent(const QModelIndex &) const {
    return QModelIndex();
}

bool PlaylistModel::hasChildren(const QModelIndex &parent) const {
    if(! parent.isValid()) {
        return true;
    }
    return false;
}



QModelIndex PlaylistModel::index(int row, int column, const QModelIndex &) const {
    return createIndex(row, column);
}



void PlaylistModel::moveUp(int index) {
    if(index > 0 && index < m_sources.size()) {
        beginMoveRows(QModelIndex(), index, index, QModelIndex(), index - 1);
        m_sources.swap(index, index - 1);
        endMoveRows();
        if(m_currentIndex == index - 1) {
            m_currentIndex = index;
        }
        else if(m_currentIndex == index) {
            m_currentIndex = index - 1;
        }
        emit playlistChanged();
    }
}



void PlaylistModel::moveDown(int index) {
    if(index >= 0 && index < m_sources.size() - 1) {
        beginMoveRows(QModelIndex(),
                      index,
                      index,
                      QModelIndex(),
                      index + 2); //i dont know how this works
        m_sources.swap(index, index + 1);
        endMoveRows();
        if(m_currentIndex == index + 1) {
            m_currentIndex = index;
        }
        else if(m_currentIndex == index) {
            m_currentIndex = index + 1;
        }
        //let us be incorrect in this case and see how it affects :)
//        cleanTempStructs(); //Reset the prev-next info
        emit playlistChanged();
    }
}



void PlaylistModel::addListener(IPlaylistListener *listner) {
    m_listeners.append(listner);
}


void PlaylistModel::notifyPlaylistChanged() {
    foreach(IPlaylistListener* lis, m_listeners) {
        lis->playlistChanged();
    }
}


void PlaylistModel::notifySelected(MediaItem *source) {
    foreach(IPlaylistListener* lis, m_listeners) {
        lis->selected(source);
    }
}


void PlaylistModel::itemRemoved(QList<QString> *ids) {
    //This is going to be a very costly affair.... but who cares hahhhahhahha
    /*The reverse iteration will make the deletion of an element irrelevant
    to the next iteration*/
    bool somethingRemoved = false;
    for(int i = m_sources.size() - 1; i >= 0 ; --i) {
        for(int j = 0; j < ids->size(); j++) {
            if(m_sources.at(i)->trackId() == ids->at(j)) {
//                m_sources.removeAt(i);
                removeItem(i);
                somethingRemoved = true;
                break;
            }
        }
    }
    if(somethingRemoved) {
        cleanTempStructs();
    }
}


void PlaylistModel::cleanTempStructs(bool rndInfoAlso) {
    if (rndInfoAlso) m_randTracks.clear();
    m_playedIndexList.clear();
    m_prevListIndex = -1;
}


void PlaylistModel::appendPlayedIndex(qint32 index) {
    if(index >= 0) {
        m_playedIndexList.append(index);
        m_prevListIndex = m_playedIndexList.size();
    }
}
